﻿import com.troyworks.util.DesignByContractError;
import com.troyworks.events.TProxy;
import com.troyworks.events.TEventDispatcher;
/*******************************************************
 * A utility to centralize tests 
 * thus during debugging there are easy ways to quickly find
 * where errors are occuring 
 * 
 * To add this to your class via a mixin style do the following:
 * 
 * 1) In the class variables add:
 * 
  	// REQUIRED by DesignByContract
	public var ASSERT : Function;
	public var REQUIRE : Function;
	 * 
 * 2) In the constructor add:
 * 
  		///add in the mixins for ASSERT and REQUIRE
 		DesignByContract.initialize(this);
 *		
 * 3) if you need access to the notification (e.g. to send errors add 
 *  a function like this
 *  	
  	//*******************************************
	 //* this function is useful for setting breakpoints 
	 //* when an assert or require fails.
	 //*
	public function onAssertFailed(evt:Object) : Void{
		trace("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ERROR" +toStringShort() + "  XXXXXXXXXXXXXXXXXX " + util.Trace.me(evt));

		if(evt.fatal){
		 (evt.msg); //NOTE that this ERROR will typically 
		}
	}
 */
class com.troyworks.util.DesignByContract extends Object {
	public static var IDz:Number = 0;
	public static var log : Array = new Array ();

	public static var IS_EQUAL_BYSTRING : String = "=string=";
	public static var IS_EQUAL : String = "==";
	public static var IS_EQUAL_TOREF : String = "===";
	public static var IS_NOT_EQUAL : String = "!=";
	public static var IS_LESS_THAN : String = "<";
	public static var IS_LESS_THAN_OR_EQUAL : String = "<=";
	public static var IS_GREATER_THAN_OR_EQUAL : String = ">=";
	public static var IS_GREATER_THAN : String = ">";
	public static var ADDED_TO_SELF_EQUALS : String = "+=";
	public static var SUBTRACTED_FROM_SELF_EQUALS : String = "-=";
	public static var DIVIDED_BY_SELF_EQUALS : String = "/=";
	public static var MULTIPLIED_BY_SELF_EQUALS : String = "*=";

	private static var bDescriptionXXX : Object;
	public static var appIsHalted : Boolean = false;
	public static var appHaltMessage:String = null;
	public static var HALT_ON_ERRORS : Boolean = false;

	public static var EVTD_REQUIRE_FAILED : String = "EVTD_REQUIRE_FAILED";
	public static var EVTD_ASSERT_FAILED : String = "EVTD_ASSERT_FAILED";
	public var dispatchEvent : Function;
//			public var eventListenerExists:Function;
	public var $tevD:TEventDispatcher;
	public var addEventListener : Function;
	public var removeEventListener : Function;
	public var removeAllEventListeners : Function;

	private static var instance : DesignByContract;

	public var ID:Number = -1;
	public var myLog : Array = new Array();

	public static var isDispatching : Boolean = false;

	public function DesignByContract() {
		ID = IDz++;
		trace(" new DesignByContract" + ID);
		
		TEventDispatcher.initialize(this);
		$tevD.debugTracesOn = true;
		addEventListener(EVTD_ASSERT_FAILED, this, this.onAssertFailed);
		addEventListener(EVTD_REQUIRE_FAILED, this, this.onAssertFailed);
	}

	public function onAssertFailed(e : Object) : Void {
		//Insert a breakpoint here.
		trace("!!!!!!!!!!!!!!!!!!!!!!!!! DesignByContract"+ID+" ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r" + util.Trace.me(e, e.message) + " fatal?: "+ e.fatal);
		if(e.fatal && DesignByContract.HALT_ON_ERRORS && !DesignByContract.appIsHalted){
			DesignByContract.appIsHalted = true;
			DesignByContract.appHaltMessage = e.msg;
		}
		
	//	throw new DesignByContractError(e);
		
	}	
		
	/**
	 * @return singleton instance of DesignByContract
	 */
	public static function getInstance() : DesignByContract {
		if (instance == null){
			instance = new DesignByContract();
		}
		return instance;
	}

	/**
	* mixin/add listening and dispatching methods to an object
	* @param object the object to receive the methods
	*/
	public static function initialize(object : Object, solo:Boolean) : Void
	{
		//trace("HIGHLIGHTP DBC.initialize");
		DesignByContract.REQUIRE(object != null, "DesignByContract.initialize(null), null is invalid argument");

		//var dbc : DesignByContract = new DesignByContract();
		//backup the pointers to the old EventDispatcher
		object.ASSERT = DesignByContract.ASSERT;
		object.REQUIRE = DesignByContract.REQUIRE;
		if(object.onAssertFailed != null){
			DesignByContract.getInstance().addEventListener(EVTD_REQUIRE_FAILED, object, object.onAssertFailed);
		}
//		object.onAssertFailed = TProxy.create(object, DesignByContract.onAssertFailed);
		//hide them in for loops
		_global.ASSetPropFlags (object, "ASSERT", 1);
		_global.ASSetPropFlags (object, "REQUIRE", 1);
		
	}
	public static function ASSERT(test : Boolean, desc : String, aFatal : Boolean) : Void
	{
		if(appIsHalted ){

			var s : String = "*** DBC isHalted ****" + DesignByContract.appHaltMessage ;
			trace(s);
			if(!isDispatching){
				trace("throwing ASSERT DesignByContractError");
		  		throw new DesignByContractError(s);
			}else{
				return;
			}
		}
		if ( ! test)
		{
			var s : String = "*** ASSERT ERROR ****"+ desc + "  failed";
			trace("ASSERT failed: " + s);
			log.push (s);
			isDispatching = true;
			DesignByContract.getInstance().dispatchEvent (
			{
				type :EVTD_ASSERT_FAILED, msg:s, fatal:aFatal
			});
			isDispatching =false;
		    
		}
	}
	public static function REQUIRE(test : Boolean, desc : String, fatal : Boolean) : Void
	{

		if(appIsHalted){
		
			var s : String = "*** DBC isHalted ****"+ DesignByContract.appHaltMessage ;
				trace(s);
			if(!isDispatching){
				trace("throwing REQUIRE DesignByContractError");
		  		throw new DesignByContractError(s);
			}else{
				return;
			}
		
		}

		if ( ! test)
		{
			var s : String = (desc == null)?"ERROR DBC BREAKPOINT":"***REQUIRE ERROR ****"+ desc + " Require failed";
			trace("REQUIRE failed: " + s);
			log.push (s);
			isDispatching = true;
			DesignByContract.getInstance().dispatchEvent (
			{
				type :EVTD_REQUIRE_FAILED, msg:s, fatal:true
			});
			isDispatching = false;
		}
	}
	public static function ASSERT_TEST(a : Object, test : String, b : Object, aDescription : String, bDescription : String, failedMessage : String) : String{
		var res : Array = new Array();
		res.push("'");
		res.push((aDescription == null)?a.toString():aDescription);
		trace("A:\r'" + a.toString() +"' \r" + test+" \r" +"B:\r'"+ b.toString()+"'");
		res.push("' " + test + " '");
		res.push((bDescriptionXXX == null)?b.toString():aDescription);
		res.push("'");
		var passes : Boolean = false;
		switch(test){
			case IS_EQUAL_BYSTRING:
				var as : String = String(a);
				var bs : String = String(b);
			//		trace("    A B:");
				var len : Number = Math.max( as.length, bs.length);
				for(var i : Number = 0; i <len; i++){
					var ac : String = (i >= as.length)?'null':as.charAt(i);
					var bc : String = (i>= bs.length)?'null':bs.charAt(i);
					var tc : Boolean = (as.charAt(i) == bs.charAt(i));
					var tcf : String = (tc)? "true" :" *** FALSE *** " ;
				//	trace(" " + i + "= "+ ac + " " + bc + " equals? " + tcf);
				}
				//	trace("    A B.");
				passes = ( as == bs);
				break;
			case IS_EQUAL:
				passes = (a == b);
				break;
			case IS_EQUAL_TOREF:
				passes = (a === b);
				break;
			case IS_NOT_EQUAL:
				passes = (a != b);
				break;
			case IS_LESS_THAN:
				passes = (a < b);
				break;
			case IS_LESS_THAN_OR_EQUAL:
				passes = (a <= b);
				break;
			case IS_GREATER_THAN:
				passes = (a > b);
				break;
			case IS_GREATER_THAN_OR_EQUAL:
				passes = (a >= b);
				break;
			/*case ADDED_TO_SELF_EQUALS:
				passes = (a += b);
				break;
			case SUBTRACTED_FROM_SELF_EQUALS:
				passes = (a -= b);
				break;
			case DIVIDED_BY_SELF_EQUALS:
				passes = (a /= b);
				break;
			case MULTIPLIED_BY_SELF_EQUALS:
				passes = (a *= b);
				break;
				*/
        }
		res.push(" passes? " + ((passes)?"TRUE":"FALSE"));
        if(!passes){
			res.push("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
			res.push(failedMessage);
			res.push("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
			var err:DesignByContractError = new DesignByContractError(failedMessage);
			DesignByContract.getInstance().onAssertFailed(err);
        	
        }
		trace("ASSERT_TEST " + res.join(""));
		return res.join("");
	}

	public static function toString() : String{
		return "DesignByContractLog:\r\t"+log.join("\r\t");
	}
	public static function clearLog() : Void
	{
		log = new Array();
	}
}
